package main

import (
	"chainproxy/config"
	"chainproxy/db"
	"chainproxy/log"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/tidwall/gjson"
	"io/ioutil"
	"strconv"
	"time"
	"unsafe"
)

var upstream string
var StorageJsonStr gjson.Result
var Storage []map[string]interface{}
var IsChange bool
var DbUps []db.Upstream

// [
//    {"addr": "http://goproxy.cn",
//      "avg_ttlb": 0,
//      "avg_ets": 0,
//      "us": [
//        {"type": "list", "avg_ttlb": 0, "avg_ets": 0, "req_ts": 0, "ttlb": 0, "ets": 0},
//        {"type": "info", "avg_ttlb": 0, "avg_ets": 0, "req_ts": 0, "ttlb": 0, "ets": 0}
//      ]}
//  ]
//  "ttlb": "请求响应时间, 单位秒",
//  "ets": "请求错误次数",
//  "req_ts": "请求次数，不包括错误次数",
//  "avg_ets": "平均发生错误概率",
//  "avg_ttlb": "平均响应时间"
func DynamicChooseUpstream(c chan gjson.Result)  {
	var err error
	if config.GetBool("db.enable"){
		err = GetUpstreamFormDB()
	}else {
		err = GetUpstreamFormLocal()
	}
	if err != nil{
		log.Logger.Error(err)
		return
	}
	if len(Storage) == 0{
		if Convert() != nil{
			log.Logger.Error(err)
			return
		}
	}
	for{
		res := <- c
		log.Logger.Info("Form chan: ", res.String())
		DealProxyRes(config.GetString("upstream.route.by"), &res)
		IsChange = true
	}
}

func TimingTask()  {
	val := config.GetString("flush.interval")
	enable := config.GetBool("db.enable")
	interval, err := time.ParseDuration(val)
	if err != nil{
		log.Logger.Error(err)
		return
	}
	for{
		time.Sleep(interval)
		if !IsChange{
			continue
		}
		log.Logger.Info("flush")
		if StorageJsonStr.String() == ""{
			continue
		}
		filename := config.GetString("upstream.local.storage")
		bs, err := json.Marshal(&Storage)
		if err != nil{
			log.Logger.Error(err)
			continue
		}
		if enable{
			SaveToDb(&bs)
			continue
		}
		err = ioutil.WriteFile(filename, bs, 0644)
		if err != nil{
			log.Logger.Error(err)
		}
		IsChange = false
	}
}

func SaveToDb(bs *[]byte){
	_ = json.Unmarshal(*bs, &DbUps)
	for i:=0;i<len(DbUps);i++{
		if DbUps[i].ID != 0{
			_ = db.UpdateAll(&DbUps[i])
		}else {
			_ = db.Insert(&DbUps[i])
		}
	}

	newBs, _ := json.Marshal(&DbUps)
	StorageJsonStr = gjson.ParseBytes(newBs)
	_ = json.Unmarshal(newBs, &Storage)
	IsChange = false
}

func DealProxyRes(route_by string, res *gjson.Result)  {
	avg_col := "avg_" + route_by
	t := res.Get("type").Str
	addr := res.Get("upstream").Str
	r := res.Get("res").Str

	path := fmt.Sprintf(`#[addr=="%s"].us.#[type=="%s"]`, addr, t)
	info := StorageJsonStr.Get(path)

	var total_req_ts, newReqTs int64
	var total_route_col, total_avg, new_route_col, newAvg float64
	path2 := fmt.Sprintf(`#[addr=="%s"].us.%s`, addr, route_by)
	path3 := fmt.Sprintf(`#[addr=="%s"].us.%s`, addr, "req_ts")
	for _, v := range StorageJsonStr.Get(path2).Array(){
		total_req_ts += v.Int()
	}
	for _, v := range StorageJsonStr.Get(path3).Array(){
		total_route_col += v.Float()
	}
	if r == "success"{
		ttlb := res.Get(route_by).Float()
		total_req_ts += 1
		total_route_col += ttlb
		new_route_col = info.Get(route_by).Float() + ttlb
		newReqTs = info.Get("req_ts").Int() + 1
		newAvg = new_route_col/float64(newReqTs)
		total_avg = total_route_col/float64(total_req_ts)
	}else {
		total_route_col += 1
		new_route_col = info.Get(route_by).Float() + 1
		newReqTs = info.Get("req_ts").Int()
		newAvg = new_route_col/(float64(newReqTs) + new_route_col)
		total_avg = total_route_col/(float64(total_req_ts) + total_route_col)
	}

	newAvg,_ = strconv.ParseFloat(fmt.Sprintf("%.3f", newAvg), 64)
	new_route_col,_ = strconv.ParseFloat(fmt.Sprintf("%.3f", new_route_col), 64)
	updateData := map[string]interface{}{
		avg_col:  newAvg,
		route_by: new_route_col,
		"req_ts": newReqTs,
	}
	UpdateStorage(addr, t, total_avg, updateData)

	path = fmt.Sprintf(`#.us.#[type=="%s"]#.%s`, t, avg_col)
	ChooseUpstream(path, addr)
}

func ChooseUpstream(path, dft string){
	addrs := StorageJsonStr.Get("#.addr").Array()
	others := StorageJsonStr.Get(path).Array()
	if len(others) == 0{
		upstream = dft
		return
	}
	min := others[0].Array()[0].Float()
	index := 0
	for i,v := range others{
		if v.Array()[0].Float() >= min{
			continue
		}
		index = i
	}
	upstream = addrs[index].String()
	fmt.Println(upstream)
}

func UpdateStorage(u, t string, total_avg float64, data map[string]interface{})  {
	var upstream map[string]interface{}
	rb := config.GetString("upstream.route.by")
	k := "avg_"+rb
	for i, m := range Storage{
		if m["addr"] != u{
			continue
		}
		Storage[i][k] = total_avg
		us,ok := m["us"].([]interface{})
		if !ok{
			continue
		}
		for _, ui := range us{
			up, ok := ui.(map[string]interface{});
			if !ok{
				continue
			}
			if up["type"] != t{
				continue
			}
			upstream = up
		}
	}
	if upstream == nil{
		return
	}
	for k,v := range data{
		upstream[k] = v
	}
	bs, err := json.Marshal(Storage)
	if err != nil{
		log.Logger.Info(err)
	}
	StorageJsonStr = gjson.ParseBytes(bs)
}

func Convert() error {
	return json.Unmarshal(Str2bytes(StorageJsonStr.String()), &Storage)
}

func Str2bytes(s string) []byte {
	x := (*[2]uintptr)(unsafe.Pointer(&s))
	h := [3]uintptr{x[0], x[1], x[1]}
	return *(*[]byte)(unsafe.Pointer(&h))
}

func GetUpstreamFormDB() error {
	upstreams := config.GetSlice("upstream")
	if len(upstreams) == 0{
		return errors.New("Failed to read 'upstream' from configuration file.")
	}
	err := db.FindAllWithPreloadUs(&DbUps)
	if err != nil{
		return err
	}
	if len(DbUps) == 0{
		InitStorageJsonStr(&upstreams)
		return nil
	}

	var addrs []string
	for _,v := range DbUps{
		addrs = append(addrs, v.Addr)
	}
	delAddr, indexs := Diff(addrs, upstreams)
	if len(delAddr) != 0{
		DeleteUpstreamFormDb(DbUps, indexs)
		IsChange = true
	}

	newAddr,_ := Diff(upstreams, addrs)

	if len(newAddr) != 0 {
		for _, v := range newAddr{
			n := db.Upstream{Addr: v, Us: []db.UpstreamStatis{
				{Type: "list", AvgTTLB: 0, AvgEts: 0 , ReqTs: 0 , TTLB: 0 , Ets: 0},
				{Type: "info", AvgTTLB: 0, AvgEts: 0 , ReqTs: 0 , TTLB: 0 , Ets: 0},
				{Type: "latest", AvgTTLB: 0, AvgEts: 0 , ReqTs: 0 , TTLB: 0 , Ets: 0},
				{Type: "mod", AvgTTLB: 0, AvgEts: 0 , ReqTs: 0 , TTLB: 0 , Ets: 0},
				{Type: "zip", AvgTTLB: 0, AvgEts: 0 , ReqTs: 0 , TTLB: 0 , Ets: 0},
			}}
			DbUps = append(DbUps, n)
		}
		IsChange = true
	}

	jsonBytes, err := json.Marshal(&DbUps)
	if err != nil{
		log.Logger.Error("json marshal failed.")
		return err
	}
	StorageJsonStr = gjson.ParseBytes(jsonBytes)
	BestUpstream()
	return nil
}

func DeleteUpstreamFormDb(ups []db.Upstream, del []int){
	var newUps []db.Upstream
	for i,_ := range ups{
		f := false
		for _,di := range del{
			if di == i{
				f = true
				break
			}
		}
		if !f{
			newUps = append(newUps, ups[i])
		}else {
			_ = db.DeleteUpstreamWithRelate(&ups[i])
		}
	}
	DbUps = newUps
}

func BestUpstream()  {
	rb := config.GetString("upstream.route.by")
	path := fmt.Sprintf(`#.avg_%s`, rb)
	avgs := StorageJsonStr.Get(path).Array()
	index := 0
	min := avgs[0].Float()
	for i,v := range avgs{
		if v.Float() < min{
			min = v.Float()
			index = i
		}
	}
	upstream = StorageJsonStr.Get("#.addr").Array()[index].String()
}

func GetUpstreamFormLocal() error {
	upstreams := config.GetSlice("upstream")
	if len(upstreams) == 0 {
		return errors.New("Failed to read 'upstream' from configuration file.")
	}
	filepath := config.GetString("upstream.local.storage")
	if !exists(filepath){
		InitStorageJsonStr(&upstreams)
		return nil
	}

	bytes, err := ioutil.ReadFile(filepath)
	if err != nil{
		log.Logger.Error("Read file failed, path = ", filepath)
		return err
	}
	if len(bytes) == 0{
		InitStorageJsonStr(&upstreams)
		return nil
	}
	if !gjson.ValidBytes(bytes){
		log.Logger.Error("Invalid json bytes.")
		return errors.New("gjson valid failed.")
	}
	gresult := gjson.ParseBytes(bytes)
	ss := gresult.Get("#.addr").Array()
	var addrs []string
	for _,v := range ss{
		addrs = append(addrs, v.Str)
	}
	delAddr, indexs := Diff(addrs, upstreams)
	if len(delAddr) != 0 {
		_ = json.Unmarshal(bytes, &Storage)
		DeleteUpstream(indexs)
		bs, _ := json.Marshal(&Storage)
		gresult = gjson.ParseBytes(bs)
	}
	newAddr,_ := Diff(upstreams, addrs)

	if len(newAddr) == 0 {
		StorageJsonStr = gresult
		BestUpstream()
		return nil
	}
	if ups, ok := gresult.Value().([]interface{});ok{
		for _, a := range newAddr{
			ups = append(ups, map[string]interface{}{"addr":a})
		}
		bs, _ := json.Marshal(ups)
		StorageJsonStr = gjson.ParseBytes(bs)
		BestUpstream()
		IsChange = true
	}
	return nil
}

func DeleteUpstream(del []int){
	var newStorage []map[string]interface{}
	for i,_ := range Storage{
		f := false
		for _,di := range del{
			if di == i{
				f = true
				break
			}

		}
		if !f{
			newStorage = append(newStorage, Storage[i])
		}
	}
	Storage = newStorage
}

func Diff(a []string, b []string) (addr []string, indexs []int) {
	for i,ad := range a{
		f := false
		for _,v := range b{
			if ad == v{
				f = true
				break
			}
		}
		if !f{
			addr = append(addr, ad)
			indexs = append(indexs, i)
		}
	}
	return
}

func InitStorageJsonStr(upstreams *[]string)  {
	var u []map[string]interface{}
	for i:=0; i<len(*upstreams); i++{
		us := []map[string]interface{}{
			{"type": "list", "avg_ttlb": 0, "avg_ets": 0, "req_ts": 0, "ttlb": 0, "ets": 0},
			{"type": "info", "avg_ttlb": 0, "avg_ets": 0, "req_ts": 0, "ttlb": 0, "ets": 0},
			{"type": "latest", "avg_ttlb": 0, "avg_ets": 0, "req_ts": 0, "ttlb": 0, "ets": 0},
			{"type": "mod", "avg_ttlb": 0, "avg_ets": 0, "req_ts": 0, "ttlb": 0, "ets": 0},
			{"type": "zip", "avg_ttlb": 0, "avg_ets": 0, "req_ts": 0, "ttlb": 0, "ets": 0},
		}

		u = append(u, map[string]interface{}{
			"addr":(*upstreams)[i],
			"avg_ttlb": 0,
			"avg_ets": 0,
			"us":us})
	}
	bytes, err := json.Marshal(&u)
	if err != nil{
		log.Logger.Error(err)
		return
	}
	StorageJsonStr = gjson.ParseBytes(bytes)
	upstream = (*upstreams)[0]
	IsChange = true
}